// ATL_Windows.cpp : Defines the entry point for the application.
//

// work item queue idea--create a special message/event in the base class called
// FINAL_EVENT. when this event is posted to the queue, the state of the queue is
// changed so that no more messages can be posted to the queue. when this message
// is processed the queue's state is updated again to indicated that it can be 
// unloaded. this means it is in a state where the module that created it could
// be unloaded safely without having to worry that the thread running that queue
// might process any more items.
//
// e.g.
//
   //bool CWorkItemQueueImpl::PostMessage(EVENT e)
   //{
   //  bool result=true;
   //  LockQueue();
   //  if (HasFinalEventBeenPosted())
   //  {
   //    result=false;
   //  }
   //  else if (static_cast<EVENT_TYPE>(e)==FINAL_EVENT)
   //  {
   //    if (PostWorkItem(e))
   //    {
   //      SetFinalEvent(true);
   //    }
   //    else
   //    {
   //      result=false;
   //    }
   //  }
   //  UnlockQueue();
   //  return result;
   //}

   //bool CWorkItemQueue::ProcessMessage(EVENT e)
   //{
   //  LockQueue();
   //  if (static_cast<EVENT_TYPE>(e)==FINAL_EVENT)
   //  {
   //    SetSafeToUnload(true);
   //  }
   //  else
   //  {
   //     HandleEvent(e)
   //  }
   //  UnLockQueue();
   //}
   //    



#include "stdafx.h"
#include "ATL_Windows.h"

#define MAX_LOADSTRING 100

// Global Variables:
//HINSTANCE hInst;								// current instance
TCHAR szTitle[MAX_LOADSTRING];					// The title bar text
TCHAR szWindowClass[MAX_LOADSTRING];			// the main window class name


class CProcessWatcher : public CDialogImpl<CProcessWatcher>
{
public:
  enum { IDD = IDD_PROCESS_MONINTOR_GENERIC };

  BEGIN_MSG_MAP(CProcessWatcher)
    MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
    COMMAND_ID_HANDLER(IDOK, OnOK) ;
  END_MSG_MAP()

  CProcessWatcher(CString& title, CString body) : m_titleText(title), m_msgText(body)
  {
  }

private:

  CString m_titleText;
  CString m_msgText;

  LRESULT OnInitDialog( UINT, WPARAM, LPARAM, BOOL&)
  {
    // set the title text
    //SendMessage(GetDlgItem(IDD_PROCESS_MONINTOR_GENERIC), WM_SETTEXT, 0, (LPARAM) m_titleText.GetString());
    SendMessage(this->m_hWnd, WM_SETTEXT, 0, (LPARAM) m_titleText.GetString());
    // set the body text
    ::SendMessage(::GetDlgItem(this->m_hWnd, IDC_STATIC_GENERIC_BODY), WM_SETTEXT, 0, (LPARAM) m_msgText.GetString());
    CenterWindow();
    ShowWindow(TRUE);
    UpdateWindow();
    return 1;
  }

  LRESULT OnOK(WORD, UINT, HWND, BOOL&) 
  {
    EndDialog(IDOK);
    return 0;
  }
};


// class for device monitoring dialog
class CDeviceWatcher : public CDialogImpl<CDeviceWatcher>
{
public:
  enum { IDD = IDD_DIALOG_DEVICE_MONITOR };

public:
  CDeviceWatcher() : m_hDeviceNotify(NULL) { }

public:
  BEGIN_MSG_MAP(CDeviceWatcher)
    MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
    COMMAND_ID_HANDLER(IDOK, OnOK);
    COMMAND_ID_HANDLER(IDC_BUTTON_REGISTER_ALL_DEVICE_INTERFACE_CHANGES, OnRegisterAllDeviceInterface)
    COMMAND_ID_HANDLER(IDC_CLEAR_DEVICE_NOTIFICATIONS, OnClearDeviceNotifications)
  END_MSG_MAP()

  // add a message to the device notification dialog
  LRESULT AddMsg(LPCTSTR msg)
  {
    return SendMessage(GetDlgItem(IDC_LIST_DEVICE_NOTIFICATIONS), LB_ADDSTRING, 0, (LPARAM) msg);
  }

  // delete the device notification list
  LRESULT OnClearDeviceNotifications(WORD, UINT, HWND, BOOL&)
  {
    return SendMessage(GetDlgItem(IDC_LIST_DEVICE_NOTIFICATIONS), LB_RESETCONTENT, 0, 0);
  }

private:
  HDEVNOTIFY m_hDeviceNotify;

private:
  LRESULT OnInitDialog( UINT, WPARAM, LPARAM, BOOL&)
  {
    CenterWindow() ;
    ShowWindow(TRUE);
    UpdateWindow();
    AddMsg(_T("registered for port device changes (all apps get these by default)"));
    AddMsg(_T("registered for disk volume changes (all apps get these by default)"));
    return 1;
  }

  LRESULT OnOK(WORD, UINT, HWND, BOOL&) 
  {
    if (m_hDeviceNotify)
    {
      UnregisterDeviceNotification(m_hDeviceNotify);
      m_hDeviceNotify=NULL;
    }
    DestroyWindow(); // <-- this is for modeless dialogs
    return 0;
  }

  // register for all USB serial host PnP drivers button handler
  LRESULT OnRegisterAllDeviceInterface(WORD, UINT, HWND, BOOL&)
  {
    //// This GUID is for all USB serial host PnP drivers, but you can replace it 
    //// with any valid device class guid.
    //GUID WceusbshGUID = { 0x25dbce51, 0x6c8f, 0x4a72, 
    //                      0x8a,0x6d,0xb5,0x4c,0x2b,0x4f,0xc8,0x35 };
    DEV_BROADCAST_DEVICEINTERFACE NotificationFilter;
    ZeroMemory(&NotificationFilter, sizeof(NotificationFilter));
    NotificationFilter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
    NotificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;  // for the handle option you need to open a device info set and use that handle
    NotificationFilter.dbcc_classguid=GUID_NULL; //WceusbshGUID;

    m_hDeviceNotify = RegisterDeviceNotification( 
      GetParent(),                        // events recipient
      &NotificationFilter,                // type of device
      DEVICE_NOTIFY_WINDOW_HANDLE |       // type of recipient handle
      DEVICE_NOTIFY_ALL_INTERFACE_CLASSES // changes on device interfaces
      );

    _ASSERT(m_hDeviceNotify!=NULL);

    if (m_hDeviceNotify) 
    {
      AddMsg(_T("registered all interface device changes"));
    }
    else
    {
      AddMsg(_T("FAILED to register for all interface device changes"));
    }

    return 0;
  }
};

// define our user messages
#define UM_MONITOR_PROCESS_THREAD_DONE  (WM_USER+100)

/**
 * rules
 *   - you can only remove a handle when it has not signaled
 *   - if a handle is signaled the execute method for that
 *     handle with be called periodically until it is reset
 */
// used with CWorkerThread 
class CSimpleThread : public IWorkerThreadClient
{
public:

  /**
   * when the thread is shutdown it will call this function with the handle
   * that the interface in the process of cleaning up. we don't want to
   * close the kernal handle that will be taken care of by the worker thread
   * class. if there is an associated context, the it should be cleaned up
   * at this point. for example, if we had several handles that have been 
   * added to the CWorkerThread class and we stored them in a std::map<CONTEXT>
   * where the handle is the KEY-VALUE, then we would want to remove the context
   * for the incoming handle at this point.
   */
  HRESULT CloseHandle(HANDLE h)
  {
    AtlTrace(_T("THREAD_EXECUTE: enter CloseHandle interface\n"));
    AtlTrace(_T("THREAD_EXECUTE: here is where any context besides the kernel object would be cleaned up\n"));
    AtlTrace(_T("THREAD_EXECUTE: Leave CloseHandle interface\n"));
    return S_OK;
  }

  /**
   * worker thread interface that is called when the handle is signalled, it
   * will called periodically while the handle is in the signalled state.
   * this is where we do our actual work that is required when the handle
   * is signalled. this implementation displays a dialog when the process 
   * associated with the handle is signalled (i.e., terminates). We will
   * first tell the main window that we got this so it will reset the
   * event so the work thread won't call it again.
   
   */
  HRESULT Execute(DWORD_PTR dwParam, HANDLE hObject)
  {
    AtlTrace(_T("THREAD_EXECUTE: enter\n"));

    CSimpleDialog<IDD_NETMONSVR_ENDED> DudeWhereIsMyCar;
    DudeWhereIsMyCar.DoModal(reinterpret_cast<HWND>(dwParam));

    /**
     * blocking sendmessage because we don't want this method to be called again because 
     * the handle is in a signalled state we are passing back to the main window the handle
     * that was signalled
     */
    AtlTrace(_T("THREAD_EXECUTE: telling our parent the we got word that our handle was signalled\n"));
    SendMessage((HWND) dwParam, UM_MONITOR_PROCESS_THREAD_DONE, (WPARAM) hObject, 0);
    AtlTrace(_T("THREAD_EXECUTE: returned from telling the main window we are done doing work\n"));

    AtlTrace(_T("THREAD_EXECUTE: leave\n"));

    return S_OK;
  }
};


HANDLE GetProcessHandle(LPCTSTR processName)
{
  HANDLE hProcessSnap=NULL;
  HANDLE hProcess=NULL;
  PROCESSENTRY32 pe32={0};

  hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);  // Takes a snapshot of all the processes

  if (hProcessSnap==INVALID_HANDLE_VALUE)
  {
    AtlTrace(_T("could not get the list of processes (rc=%d)\n"), ::GetLastError());
  }
  else
  {
    pe32.dwSize = sizeof(PROCESSENTRY32);

    if (Process32First(hProcessSnap, &pe32))
    {
      do
      {
        AtlTrace(_T("Found process: %s\n"), pe32.szExeFile);
        if (wcscmp(CT2W(pe32.szExeFile), _T("NetCfgSvr.exe"))==S_OK)
        {
          AtlTrace(_T("found the process to monitor\n"));

          // gets handle to process with SYNCHRONIZE rights
          hProcess = OpenProcess(SYNCHRONIZE, FALSE, pe32.th32ProcessID);  
          
          if (hProcess) // this will need to be closed later
          {
            AtlTrace(_T("opened the process handle '%p'\n"), hProcess);
            break;
          }
          else
          {
            AtlTrace(_T("could not open the process handle\n"));
          }
        }
      } while (Process32Next(hProcessSnap, &pe32));  // gets next member of snapshot
    }
  }

  if (hProcess==NULL)
  {
    AtlTrace(_T("could not find the target process '%s' handle\n"), processName);
  }
  if (hProcessSnap) CloseHandle(hProcessSnap);  // closes the snapshot handle

  return hProcess;
}


/**
 * this is an ATL window class
 */
class CMainWindow : public CWindowImpl<CMainWindow>
{
public:
  
  CMainWindow() : m_pWorkerThreadImpl(NULL)
  {
    // default constructor    
  }

  BEGIN_MSG_MAP(CMainWindow)
    MESSAGE_HANDLER(WM_CREATE, OnCreate)
    MESSAGE_HANDLER(WM_PAINT, OnPaint)
    MESSAGE_HANDLER(WM_QUIT, OnDestroy)
    MESSAGE_HANDLER(WM_DEVICECHANGE, OnDeviceChange)
    MESSAGE_HANDLER(UM_MONITOR_PROCESS_THREAD_DONE, OnWorkDone)
    COMMAND_HANDLER(ID_TOOLS_DEVICEEVENTMONITORING, 0, OnMenuToolsDeviceMon)
    COMMAND_HANDLER(ID_TOOLS_AGNPROCESSMONITORING,  0, OnMenuToolsProcessMonitoring)
    COMMAND_HANDLER(ID_SENDFAKEPROCESSENDNOTIFICATION, 0, OnMenuToolsSendFakeProcessEnd)
    COMMAND_HANDLER(ID_TOOLS_END_PROCESS_MONITORING, 0, OnMenuToolsEndProcessMonitoring)
  END_MSG_MAP()
  
private:
  CDeviceWatcher DeviceWatcherDialog;
  CWindow m_DeviceWatcherDlg;
  CWorkerThread<Win32ThreadTraits> myThread;  // the ATL class object
  CSimpleThread *m_pWorkerThreadImpl;         // impl of the ATL worker thread interface that is called back by the worker
  HANDLE m_hProcessHandle;                     // event that is signaled to make the worker thread do some work

private:

  // implements the functionality for the main window for what should be done 
  // after the handle has been signalled, we have to pass in the handle so we
  // can lookup the associated context by using the handle
  LRESULT OnWorkDone(UINT nMsg, WPARAM wparam, LPARAM lparam, BOOL& bHandled)
  {
    HANDLE contextHandle=reinterpret_cast<HANDLE>(wparam);
    AtlTrace(_T("enter OnWorkDone (mesg_code=%d, handled=%d)\n"), nMsg, bHandled);
    AtlTrace(_T("work thread has done its job and told us that the process ended...reset the handle\n"));
    ResetEvent(contextHandle);  // this will stop the worker thread from sending notifications on this handle
    AtlTrace(_T("leave OnEndProcessMonitoring\n"));
    return 0;
  }

  // stop the worker thread @todo-new to destroy the interface object too
  LRESULT OnMenuToolsEndProcessMonitoring(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
  {
    AtlTrace(_T("enter OnMenuToolsEndProcessMonitoring (notify_code=%d, ID=%d)\n"), wNotifyCode, wID);
    HRESULT hr=myThread.Shutdown(5000);
    AtlTrace(_T("did the shutdown work returned: %p\n"), hr);
    if (m_pWorkerThreadImpl) delete m_pWorkerThreadImpl;
    AtlTrace(_T("leave OnMenuToolsEndProcessMonitoring\n"));
    return 0;
  }

  // sending the fake process end notification, this will set the event
  LRESULT OnMenuToolsSendFakeProcessEnd(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
  {
    AtlTrace(_T("enter OnMenuToolsSendFakeProcessEnd (notify_code=%d, ID=%d)\n"), wNotifyCode, wID);
    AtlTrace(_T("setting event to simulate that the process has ended\n"));
    SetEvent(m_hProcessHandle);
    AtlTrace(_T("leave OnMenuToolsSendFakeProcessEnd\n"));
    return 0;
  }

  // called when the user selects the process monitoring "menu->tools->process_monitor"
  LRESULT OnMenuToolsProcessMonitoring(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
  {
    AtlTrace(_T("enter OnMenuToolsProcessMonitoring (notify_code=%d, ID=%d)\n"), wNotifyCode, wID);
    m_hProcessHandle=GetProcessHandle(_T("netcfgsvr.exe"));
    if (!m_hProcessHandle)
    {
      CProcessWatcher noProc(CString("netcfgsvr.exe process not found"),
                             CString("the netmon service process is not running, run 'net start netcfgsvr'"));
      noProc.DoModal(this->m_hWnd);
      return 0;
    }
    m_pWorkerThreadImpl=new CSimpleThread();  // create an instance of our implementation of IWorkerThreadClient
    myThread.Initialize();  // this is the ATL thread class instance
    AtlTrace(_T("got the process handle (%p)\n"), m_hProcessHandle);

    // add the:
    //   1. process handle
    //   2. window handle 
    //   3. instance of our implementation of IWorkerThreadClient 
    // to the worker thread object
    myThread.AddHandle(m_hProcessHandle, m_pWorkerThreadImpl, (DWORD_PTR) this->m_hWnd);  
    AtlTrace(_T("adding the handle that will signal it to do working to the thread object (handle=%p)\n"), m_hProcessHandle);
    AtlTrace(_T("leave OnMenuToolsProcessMonitoring\n"));
    return 0;
  }

  // called when the menu item device monitoring is selected from the windows app menu
  LRESULT OnMenuToolsDeviceMon(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
  {
    m_DeviceWatcherDlg=DeviceWatcherDialog.Create(m_hWnd);
    return 0;
  }

  // called right before the main window is created
  LRESULT OnCreate(UINT nMsg, WPARAM wparam, LPARAM lparam, BOOL& bHandled)
  {
    return 0;
  }

  // handler for WM_DEVICECHANGES
  LRESULT OnDeviceChange(UINT nMsg, WPARAM wparam, LPARAM lparam, BOOL& bHandled);

  // put the pixels in the basket
  LRESULT OnPaint(UINT nMsg, WPARAM wparam, LPARAM lparam, BOOL& bHandled)
  {
    PAINTSTRUCT ps;
    HDC         hdc = BeginPaint(&ps) ;
    RECT        rect; GetClientRect(&rect) ;
    DrawText(hdc, __T("Hello, Windows") , -1, &rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE) ;
    EndPaint(&ps) ;
    return 0;
  }

  // called right before the window is destructed 
  LRESULT OnDestroy(UINT nMsg, WPARAM wparam, LPARAM lparam, BOOL& bHandled)
  {
    PostQuitMessage(0);
    return 0;
  }
};

/**
 * class for process warning dialogs
 */
//class CProcessWatcher : public CDialogImpl<CProcessWatcher>
//{
//public:
//  enum { IDD = IDD_ABOUTBOX };
//
//public:
//BEGIN_MSG_MAP(CAboutBox)
//  MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
//  COMMAND_ID_HANDLER(IDOK, OnOK) ;
//END_MSG_MAP()
//
//private:
//  LRESULT OnInitDialog( UINT, WPARAM, LPARAM, BOOL&)
//  {
//    CenterWindow() ;
//    return 1;
//  }
//  LRESULT OnOK(WORD, UINT, HWND, BOOL&) 
//  {
//    EndDialog(IDOK) ;
//    return 0;
//  }
//};





/**
  The system broadcasts the DBT_DEVNODES_CHANGED device event when a device has been 
  added to or removed from the system. Applications that maintain lists of devices 
  in the system should refresh their lists.
*/
LRESULT CMainWindow::OnDeviceChange(UINT nMsg, WPARAM wparam, LPARAM lparam, BOOL& bHandled)
{
  PDEV_BROADCAST_HDR pdev = reinterpret_cast<PDEV_BROADCAST_HDR>(lparam);
  AtlTrace(_T("There was a device changes notification '%p' (device_info=%p)\n"), wparam, lparam);

  switch (wparam)
  {
  case DBT_DEVNODES_CHANGED:
    AtlTrace(_T("DBT_DEVNODES_CHANGED\n"));
    DeviceWatcherDialog.AddMsg(_T("device node has changed"));
    break;
  case DBT_DEVICEARRIVAL:
  {
    AtlTrace(_T("DBT_DEVICEARRIVAL (type=%p, size=%lu):\n"), pdev->dbch_devicetype, pdev->dbch_size);
    // format the notification message based on the device type that was removed
    CString msg;
    switch (pdev->dbch_devicetype)
    {
    case DBT_DEVTYP_DEVICEINTERFACE:
    {
      PDEV_BROADCAST_DEVICEINTERFACE pdevi = reinterpret_cast<PDEV_BROADCAST_DEVICEINTERFACE>(lparam);
      msg.Format(_T("DBT_DEVICEARRIVAL: %s"), pdevi->dbcc_name);
      break;
    }
    case DBT_DEVTYP_VOLUME:
      msg.Format(_T("DBT_DEVICEARRIVAL: %s"), _T("disk volume"));
      break;
    default:
       msg.Format(_T("DBT_DEVICEARRIVAL: handled type (%d)"), pdev->dbch_devicetype);
       break;
    }
    DeviceWatcherDialog.AddMsg(msg.GetString());
    break;
  }
  case DBT_DEVICEREMOVECOMPLETE:
  {
    AtlTrace(_T("DBT_DEVICEREMOVECOMPLETE (type=%p, size=%lu)\n"), pdev->dbch_devicetype, pdev->dbch_size);
    // format the notification message based on the device type that was removed
    CString msg;
    switch (pdev->dbch_devicetype)
    {
    case DBT_DEVTYP_DEVICEINTERFACE:
    {
      PDEV_BROADCAST_DEVICEINTERFACE pdevi = reinterpret_cast<PDEV_BROADCAST_DEVICEINTERFACE>(lparam);
      msg.Format(_T("DBT_DEVICEREMOVECOMPLETE: %s"), pdevi->dbcc_name);
      break;
    }
    case DBT_DEVTYP_VOLUME:
      msg.Format(_T("DBT_DEVICEREMOVECOMPLETE: %s"), _T("disk volume"));
      break;
    default:
       msg.Format(_T("DBT_DEVICEREMOVECOMPLETE: unhandled type (%d)"), pdev->dbch_devicetype);
       break;
    }
    DeviceWatcherDialog.AddMsg(msg.GetString());
    break;
  }
  case DBT_CONFIGCHANGECANCELED:
  {
    AtlTrace(_T("DBT_CONFIGCHANGECANCELED\n"));
    CString msg;
    switch (pdev->dbch_devicetype)
    {
    default:
       msg.Format(_T("DBT_CONFIGCHANGEPDEV_BROADCAST_DEVICEINTERFACECANCELED device (%d)"), pdev->dbch_devicetype);
       break;
    }
    DeviceWatcherDialog.AddMsg(msg.GetString());
    break;
  }
  default:
  {
    CString msg;
    AtlTrace(_T("Unhandled device notification\n"));
    msg.Format(_T("unhandled device notification (type=%p)"), wparam);
    DeviceWatcherDialog.AddMsg(msg.GetString());
    break;
  }
  }
  return 0;
}

/** 
 * Win32 GUI application entry point
 */
int APIENTRY _tWinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPTSTR    lpCmdLine,
                     int       nCmdShow)
{
	UNREFERENCED_PARAMETER(hPrevInstance);
	UNREFERENCED_PARAMETER(lpCmdLine);

 	// TODO: Place code here.
	MSG msg;
	HACCEL hAccelTable;

	// Initialize global strings
	LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
	LoadString(hInstance, IDC_ATL_WINDOWS, szWindowClass, MAX_LOADSTRING);

  // instance of our CWindow implementation
  CMainWindow wnd;

  // btw, When a process terminates, the state of the process object becomes signaled, 
  // releasing any threads that had been waiting for the process to terminate. For 
  // more about synchronization, see Synchronizing Execution of Multiple Threads.



  // load our menu resource and pass it to the create in the last param
  HMENU hMenu=LoadMenu(hInstance, MAKEINTRESOURCE(IDC_ATL_WINDOWS));

  wnd.Create(0, CWindow::rcDefault, szTitle, WS_OVERLAPPEDWINDOW, WS_EX_CLIENTEDGE, hMenu);

  if (!wnd)
  {
    return FALSE;
  }

  wnd.CenterWindow();
  wnd.ShowWindow(nCmdShow);
  wnd.UpdateWindow();

	hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_ATL_WINDOWS));

	// Main message loop:
	while (GetMessage(&msg, NULL, 0, 0))
	{
		if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}

	return (int) msg.wParam;
}



